feat(mcp-server): add AsyncHyperpingClient support to create_mcp_server#46
feat(mcp-server): add AsyncHyperpingClient support to create_mcp_server#46KhaledSalhab-Develeap wants to merge 2 commits into
Conversation
Add async test fixtures and classes to all 8 MCP server test files, covering coroutine dispatch for AsyncHyperpingClient and AsyncHyperpingMcpClient tool registrations.
…er (#48d94e) Register tools as async coroutines when AsyncHyperpingClient or AsyncHyperpingMcpClient is passed. Each register_*_tools function uses isinstance dispatch at registration time so async and sync clients can be mixed (e.g. async REST + sync MCP). FastMCP runs coroutine tools natively on the event loop.
|
Reviewer context (not a merge request): Extends Where to focus review: The Risks / verify: Code duplication is the main maintenance risk; a per-method dispatch helper would be safer. Verify FastMCP actually awaits coroutine-registered tools, and that CI status: No checks triggered. This PR targets Notes: Stacked on #43. Review after #43 lands. Large duplication but additive and backward-compatible. |
Summary
create_mcp_server()now acceptsAsyncHyperpingClientandAsyncHyperpingMcpClientin addition to their sync counterpartsregister_*_tools()function usesisinstancedispatch at registration time to define eitherasync defordeftool closures; FastMCP runs coroutine tools natively on the event loopWhat changed
src/hyperping/mcp_server/__init__.pyclientandmcp_clientparameter types to include async variantssrc/hyperping/mcp_server/_registry.pyregister_toolssignature; added async type importssrc/hyperping/mcp_server/_tools_monitors.pyAsyncHyperpingClient, sync otherwise; same pattern forsearch_monitors_by_nameonmcp_clientsrc/hyperping/mcp_server/_tools_incidents.pysrc/hyperping/mcp_server/_tools_maintenance.pysrc/hyperping/mcp_server/_tools_outages.pysrc/hyperping/mcp_server/_tools_statuspages.pysrc/hyperping/mcp_server/_tools_healthchecks.pysrc/hyperping/mcp_server/_tools_observability.pymcp_clientfor 15 observability toolstests/unit/test_mcp_server/test_factory.pyTestCreateServerWithAsyncClient(6 tests)tests/unit/test_mcp_server/test_tools_monitors.pyTestMonitorToolsAsync(5 tests)tests/unit/test_mcp_server/test_tools_observability.pyTestObservabilityToolsAsync(1 test)tests/unit/test_mcp_server/test_tools_incidents.pyTestIncidentToolsAsync(1 smoke test)tests/unit/test_mcp_server/test_tools_maintenance.pyTestMaintenanceToolsAsync(1 smoke test)tests/unit/test_mcp_server/test_tools_outages.pyTestOutageToolsAsync(1 smoke test)tests/unit/test_mcp_server/test_tools_statuspages.pyTestStatusPageToolsAsync(1 smoke test)tests/unit/test_mcp_server/test_tools_healthchecks.pyTestHealthcheckToolsAsync(1 smoke test)Design reasoning
Inline
isinstancedispatch was chosen over separate async tool files. Each registration function reads cleanly as one function with two branches: the branch selected at server-startup time, not at call time. The approach avoids code duplication across sync and async function bodies (only theawaitkeyword differs), and keeps the public API surface identical regardless of which client type is passed.MagicMock(spec=AsyncHyperpingClient)satisfiesisinstance(mock, AsyncHyperpingClient)via Mock's__class__property, so no special async-mock plumbing is needed for the dispatch path in tests.Verification matrix
uv run pytest tests/unit/test_mcp_server/ --no-cov -quv run mypy src/hyperping/mcp_server/ --ignore-missing-importsuv run ruff check src/hyperping/mcp_server/ tests/unit/test_mcp_server/uv run ruff format --check src/ tests/Acceptance criteria
create_mcp_server(client=AsyncHyperpingClient(...))returns a valid FastMCP serverasyncio.iscoroutinefunctionreturns True)